﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows.Forms;
using NesGfx;

namespace cnetmegamanprj
{
    class MegaMan2 : MegaMan
    {
        public override int GameNumber { get { return 2; } }
        public override int levelCount { get { return 100; } }
        public MegaMan2(Rom rom_) : base(rom_)
        {
            LoadJson(2);
        }
        public override bool testFunc()
        {
            //MessageBox.Show(json.chrBankOffset.ToString());
            for (int i = 0; i < 8; i++)
            {
                getMapData(i);
            }
            return false;
        }

        void updateLevel(int levelNumber)
        {
            var bankSubLevel = (levelNumber & 8) >> 3;
            var bank = levelNumber & 7;

            int levelBase = (json.lvlBankSz * bank) + json.lvlBankOffset;

            levelPtr.palSet = (bankSubLevel == 0 ? json.levelBank.palSet : json.levelBank2.palSet) + levelBase;
            levelPtr.tsaTile = json.graphicsBank.tsaTile + levelBase;
            levelPtr.tsaPal = json.graphicsBank.tsaPal + levelBase;
            levelPtr.screen = json.graphicsBank.screen + levelBase;

            int a = bankSubLevel * 3;
            levelPtr.levelStartScr = rom[json.levelBank.startPointScreen + levelBase + a];
            if (bankSubLevel == 0)
                levelPtr.levelEndScr = rom[json.levelBank.startPointScreen + levelBase + 3];
            else
                levelPtr.levelEndScr = json.maxScrPreset + 1;

            int levelTileIx = (bank * 0x400) + 0x200 + (bankSubLevel * 0xA0);

            //Load default tiles
            for (int b = 0; b < 0x60; b++)
            {
                ChrMapBG[b] = 0x2400 + b;
            }
            //Load level-specific tiles
            for (int b = 0; b < 0xA0; b++)
            {
                ChrMapBG[b + 0x60] = levelTileIx;
                levelTileIx++;
            }

        }



        //MM2
        byte[,] getMapData1()
        {
            var scrPresNumOf = json.maxScrPreset + 1;

            //Disable comments to make it function like before. (and Saving will work.):
            //levelPtr.levelStartScr = 0;
            //levelPtr.levelEndScr = scrPresNumOf;

            var levelNumOfScreens = (levelPtr.levelEndScr - levelPtr.levelStartScr);

            byte[,] scrArr = new byte[levelNumOfScreens, 0x100];
            //byte[] scr_order_arr = new byte[scrPresNumOf]; //not needed yet
            byte[,] scrPalArr = new byte[levelNumOfScreens, 0x100];
            byte[,] scrPropertyArr = new byte[levelNumOfScreens, 0x100];
            scr_pal_arr_ = scrPalArr;
            scr_property_arr_ = scrPropertyArr;

            var scrBasePtr = levelPtr.levelStartScr * 0x40;

            for (var scr = 0; scr < levelNumOfScreens; scr++)
            {
                for (var striy = 0; striy < 8; striy++)
                {
                    for (var strix = 0; strix < 8; strix++)
                    {
                        var tsaIx = rom[levelPtr.screen + scrBasePtr + (strix << 3) + striy];
                        var strRdPtr = levelPtr.tsaTile + (tsaIx << 2);
                        //var strRdPtr = levelPtr.tsaTile + (rom[levelPtr.screen + scrBasePtr + (strix << 3) + striy] << 2);
                        var scrWrPtr = (striy << 5) | (strix << 1);

                        scrArr[scr, scrWrPtr] = (byte) (rom[strRdPtr + 0] & 0x3F);
                        scrArr[scr, scrWrPtr | 1] = (byte) (rom[strRdPtr + 2] & 0x3F);
                        scrArr[scr, scrWrPtr | 0x10] = (byte) (rom[strRdPtr + 1] & 0x3F);
                        scrArr[scr, scrWrPtr | 0x11] = (byte) (rom[strRdPtr + 3] & 0x3F);

                        int a = rom[levelPtr.tsaPal + tsaIx];
                        scrPalArr[scr, scrWrPtr | 0x11] = (byte) ((a >> 6) & 3);
                        scrPalArr[scr, scrWrPtr | 0x10] = (byte) ((a >> 4) & 3);
                        scrPalArr[scr, scrWrPtr | 0x1] = (byte) ((a >> 2) & 3);
                        scrPalArr[scr, scrWrPtr | 0x0] = (byte) (a & 3);

                        scrPropertyArr[scr, scrWrPtr] = (byte) (rom[strRdPtr + 0] >> 6);
                        scrPropertyArr[scr, scrWrPtr | 1] = (byte) (rom[strRdPtr + 2] >> 6);
                        scrPropertyArr[scr, scrWrPtr | 0x10] = (byte) (rom[strRdPtr + 1] >> 6);
                        scrPropertyArr[scr, scrWrPtr | 0x11] = (byte) (rom[strRdPtr + 3] >> 6);
 
                    }
                }
                scrBasePtr += 0x40;
            }

            //MM.romcpy(scr_order_arr, levelPtr.ScrLayout, scrPresNumOf); //note: not need for now.

            //todo: Pass scrolling information in array
            //todo: Pass screen order array

            int minn = Int16.MaxValue, maxx = Int16.MinValue;
            foreach (var v in scrPropertyArr)
            {
                if (v < minn)
                    minn = v;
                if (v > maxx)
                    maxx = v;
            }

            return scrArr;

        }


        public override void writeMapData(byte[,] map, byte[,] palmap, byte[,] scr_property_arr)
        {
            int scrPresetNumOf = json.maxScrPreset + 1;

            byte[,] propertymap = scr_property_arr;
            //int[,]propertymap = new int[scrPresetNumOf, 0x100];

            int[] bufTsa32 = new int[4];
            int[] bufPal = new int[4];
            int bufPalByte;

            int[,] regTsa32 = new int[256, 4];
            int[] regTsaPalByte = new int[256];
            int[, ,] regScreen = new int[scrPresetNumOf, 8, 8];

            int registeredTsa = 0;
            bool limitReached = false;
            bool conversionOverFlow = false;

            int useTsa;

            for (int strY = 0; strY < 8; strY++)
            {
                for (int strX = 0; strX < 8; strX++)
                {
                    int srcTsa32Pointer = (strY << 5) | (strX << 1);

                    for (int scr = 0; scr < scrPresetNumOf; scr++)
                    {

                        bufTsa32[0] = map[scr, (srcTsa32Pointer)] |
                            (propertymap[scr, (srcTsa32Pointer)] << 6);
                        bufTsa32[1] = map[scr, (srcTsa32Pointer + 1)] |
                            (propertymap[scr, (srcTsa32Pointer + 1)] << 6);
                        bufTsa32[2] = map[scr, (srcTsa32Pointer + 16)] |
                            (propertymap[scr, (srcTsa32Pointer + 16)] << 6);
                        bufTsa32[3] = map[scr, (srcTsa32Pointer + 17)] |
                            (propertymap[scr, (srcTsa32Pointer + 17)] << 6);

                        bufPal[0] = palmap[scr, (srcTsa32Pointer)];
                        bufPal[1] = palmap[scr, (srcTsa32Pointer + 1)];
                        bufPal[2] = palmap[scr, (srcTsa32Pointer + 16)];
                        bufPal[3] = palmap[scr, (srcTsa32Pointer + 17)];
                        bufPalByte = bufPal[0] | (bufPal[1] << 2) |
                                    (bufPal[2] << 4) | (bufPal[3] << 6);

                        if (registeredTsa == 0)
                        { goto register_tsa; }

                        //Check if 32x32 tsa is already registered
                        for (int tsaCount = 0; tsaCount < registeredTsa; tsaCount++)
                        {
                            bool simi = true;

                            simi &= (bufTsa32[0] == regTsa32[tsaCount, 0]);
                            simi &= (bufTsa32[1] == regTsa32[tsaCount, 1]);
                            simi &= (bufTsa32[2] == regTsa32[tsaCount, 2]);
                            simi &= (bufTsa32[3] == regTsa32[tsaCount, 3]);
                            simi &= (bufPalByte == regTsaPalByte[tsaCount]);

                            if (simi == true)
                            {
                                //Found similar TSA
                                useTsa = tsaCount; goto hastsa;
                            }

                        }
                    register_tsa:
                        //if all 32x32 TSA are used up, use first and don't register.
                        if (limitReached == true)
                        {
                            conversionOverFlow = true;
                            useTsa = 0;
                            goto hastsa;
                        }

                        regTsa32[registeredTsa, 0] = bufTsa32[0];
                        regTsa32[registeredTsa, 1] = bufTsa32[1];
                        regTsa32[registeredTsa, 2] = bufTsa32[2];
                        regTsa32[registeredTsa, 3] = bufTsa32[3];
                        regTsaPalByte[registeredTsa] = (byte)bufPalByte;

                        useTsa = (byte)registeredTsa;
                        registeredTsa++;
                        if (registeredTsa == 256)
                        {
                            limitReached = true;
                        }
                    hastsa:
                        regScreen[scr, strY, strX] = useTsa;

                    }

                }
            }



            bool writeBack = true;
            if (conversionOverFlow == true)
            {
                DialogResult result = MessageBox.Show(
                "Not enough space to cover all the generated data. The result will look bad. Keep conversion?",
                "32x32 TSA limit reached.",
                MessageBoxButtons.YesNo);
                if (result == DialogResult.No) { writeBack = false; }
            }

            if (writeBack == false) { return; }

            for (int scr = 0; scr < scrPresetNumOf; scr++)
            {
                for (int strY = 0; strY < 8; strY++)
                {
                    for (int strX = 0; strX < 8; strX++)
                    {
                        rom[levelPtr.screen + (scr * 0x40) + (strX * 8) + strY] = (byte)regScreen[scr, strY, strX];
                    }
                }
            }

            for (int tsa = 0; tsa < 0x100; tsa++)
            {
                int tsa32writePtr = levelPtr.tsaTile + (tsa * 4);
                rom[tsa32writePtr + 0] = (byte)regTsa32[tsa, 0];
                rom[tsa32writePtr + 1] = (byte)regTsa32[tsa, 2];
                rom[tsa32writePtr + 2] = (byte)regTsa32[tsa, 1];
                rom[tsa32writePtr + 3] = (byte)regTsa32[tsa, 3];

                rom[levelPtr.tsaPal + tsa] = (byte)regTsaPalByte[tsa];
            }

        }


        public override void drawTiles(int level, Render render)
        {
            updateLevel(level);
            render.tile.set(rom.prg);
            render.pal.setPalette(rom.rom, levelPtr.palSet + 2);
            //render tiles
            {
                var c = 0;
                for (int y = 0; y < 16; y++)
                {
                    for (int x = 0; x < 16; x++)
                    {
                        var d = ChrMapBG[c];
                        render.Draw(d, x * 8 * (int)render.mplX, y * 8 * (int)render.mplY);
                        c++;
                    }
                }
            }
        }


        public override void loadTsa(int level, TSAEditData tsa)
        {
            updateLevel(level);
            TSAEditData d = tsa;//new TSAEditData();
            //edit by mx (mm2 doesnt use CHR-ROM)
            d.chrdata = rom.prg;
            d.displayC = 8;
            d.displayR = 32;
            d.len = 256;
            d.offset = 0;
            d.tileMap = (int[])ChrMapBG.Clone();
            getTSAEditData1(tsa);
        }
        void getTSAEditData1(TSAEditData td)
        {
            byte[] TSA_tile_arr = new byte[0x400];
            byte[] TSA_pal_arr = new byte[0x100];
            byte[] pal_arr = new byte[0x10];

            for (int a = 0, b = 0; a < 0x40; a++, b += 4)
            {
                TSA_tile_arr[b + 0] = (byte)(b + 0);
                TSA_tile_arr[b + 1] = (byte)(b + 2);
                TSA_tile_arr[b + 2] = (byte)(b + 1);
                TSA_tile_arr[b + 3] = (byte)(b + 3);

                TSA_pal_arr[a] = 0; // (byte)(MM[LevelPtr.TSA_type + a] & 3);
            }

            for (int i = 0; i < pal_arr.Length; i++)
            {
                pal_arr[i] = rom[levelPtr.palSet + 2 + i];
            }

            td.tsa = new TSA(TSA_tile_arr, 2);
            td.highBits = TSA_pal_arr;
            td.pal = pal_arr;
        }



        public override TileEditData getTileEditData()
        {
            TileEditData t = new TileEditData();
            //edit by mx (mm2 doesnt use CHR-ROM)
            t.buf = rom.prg;
            t.offset = 0;
            t.amount = t.buf.Length / 16;
            t.map = (int[])ChrMapBG.Clone();
            t.c = 16;
            t.r = 16;
            return t;
        }

        byte[,] scr_pal_arr_, scr_property_arr_;
        public override LevelData getLevelData(int level)
        {
            LevelData w = new LevelData();
            w.tsaEdit = new TSAEditData();
            loadTsa(level, w.tsaEdit);
            w.worldMap = getMapData(level); //brb.fill_map_editor_data();
            w.paletteIndex = scr_pal_arr_;
            w.attr = scr_property_arr_;
            w.tsaCol = w.tsaRow = 16;
            return w;
            //return new LevelData();
        }

        public override void SaveLevelData(LevelData lvl)
        {
            writeMapData(lvl.worldMap, scr_pal_arr_, scr_property_arr_);
        }

        byte[,] getMapData(int level)
        {
            updateLevel(level);
            return getMapData1();
        }

    }
}

